using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Data.Objects;
using System.Data.Objects.DataClasses;
using System.Reflection;
using Common.Core;
using Microsoft.Practices.ObjectBuilder2;
using Common.Unity.Extensions;
using Guard=Microsoft.Practices.ObjectBuilder2.Guard;

namespace Common.Unity.Extensions.Entity
{
	class EntityQueryRetrievalPolicy : EntityCreationPolicyBase
	{
		const string ResolveQueryName = "RetrieveResult";
		readonly static MethodInfo method = typeof(EntityQueryRetrievalPolicy).GetMethod( ResolveQueryName, CommonBindingFlags.AllProperties );
		
		readonly string query;
		readonly IEnumerable<ObjectParameterCreator> parameterCreators;
		readonly IEnumerable<string> includes;

		public EntityQueryRetrievalPolicy( ObjectContext objectContext, IEntityCreator creator, string query, IEnumerable<ObjectParameterCreator> parameterCreators ) : this( objectContext, creator, query, parameterCreators, null )
		{}

		public EntityQueryRetrievalPolicy( ObjectContext context, IEntityCreator creator, string query, IEnumerable<ObjectParameterCreator> parameterCreators, IEnumerable<string> includes ) : base( context, creator )
		{
			Guard.ArgumentNotNullOrEmpty( query, "query" );
			Guard.ArgumentNotNull( parameterCreators, "creators" );
			
			this.query = query;
			this.parameterCreators = parameterCreators;
			this.includes = includes ?? new string[0];
		}

		ObjectQuery<ItemType> ProcessIncludes<ItemType>( ObjectQuery<ItemType> objectQuery )
		{
			foreach ( var item in includes )
			{
				objectQuery = objectQuery.Include( item );
			}
			return objectQuery;
		}

		object RetrieveResult<ItemType>( Type elementType, ObjectParameter[] parameters )
		{
			var objectQuery = ProcessIncludes( EntityContext.CreateQuery<ItemType>( query, parameters ) );
			return elementType == typeof(ItemType) ? (object)objectQuery.FirstOrDefault() : objectQuery;
		}

		IEnumerable<ObjectParameter> ResolveParameters( IBuilderContext context )
		{
			foreach ( var item in parameterCreators )
			{
				var parameter = item.CreateParameter( context );
				if ( parameter != null )
				{
					yield return parameter;
				}
			}
		}

		protected override object RetrieveExistingEntity( IBuilderContext context )
		{
			Type type;
			if ( BuildKey.TryGetType( context.BuildKey, out type ) )
			{
				var elementType = ListHelper.ResolveElementType( type ) ?? type;
				var result = method.MakeGenericMethod( elementType ).Invoke( this, new object[] { type, ResolveParameters( context ).ToArray() } );
				return result;
			}
			return null;
		}
	}
}